package generator

import (
	"fmt"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"sort"
	"strings"
	"v8s/compose/model"
)

type (
	LoadBalancerServiceGenerator struct {
	}
)

const (
	LoadBalancerServicePortsTemplate       = "platform-v.drop-app/%s-ports"
	LoadBalancerServicePortsValueDelimiter = ","

	loadBalancerName = "load-balancer"

	loadBalancerPodSelector      = "platform-v.drop-app/loadbalanced"
	loadBalancerPodSelectorValue = "true"
)

func NewLoadBalancerServiceGenerator() *LoadBalancerServiceGenerator {
	return &LoadBalancerServiceGenerator{}
}

func (g *LoadBalancerServiceGenerator) Generate(services model.Services) ([]*corev1.Service, error) {
	uniquePorts := map[int32]*model.PortConfiguration{}
	resourceBelongsTo := make([]string, 0)
	annotations := map[string]string{}

	for name, service := range services {
		if !g.isLoadBalanced(service) {
			continue
		}

		resourceBelongsTo = append(resourceBelongsTo, string(name))
		ports := make([]string, 0, len(service.Ports))

		for _, port := range service.Ports {
			configuration, err := port.ToPortConfiguration()
			if err != nil {
				return nil, fmt.Errorf("failed to parse port of service '%s': %w", name, err)
			}

			if _, ok := uniquePorts[configuration.HostPort]; ok {
				return nil, fmt.Errorf("host port '%d' is alredy in use", configuration.HostPort)
			}

			uniquePorts[configuration.HostPort] = configuration
			ports = append(ports, fmt.Sprintf("%d", configuration.HostPort))
		}

		annotations[fmt.Sprintf(LoadBalancerServicePortsTemplate, name)] = strings.Join(ports, LoadBalancerServicePortsValueDelimiter)
	}

	if len(uniquePorts) == 0 {
		return nil, nil
	}

	ports := make([]corev1.ServicePort, 0, len(uniquePorts))

	for _, port := range uniquePorts {
		var protocol corev1.Protocol

		switch port.Protocol {
		case model.ProtocolTcp:
			protocol = corev1.ProtocolTCP
		default:
			return nil, fmt.Errorf("port protocol '%s' is not supported for loadbalancing yet", port.Protocol)
		}

		ports = append(ports, corev1.ServicePort{
			Name:       port.Identity(),
			Protocol:   protocol,
			Port:       port.HostPort,
			TargetPort: intstr.FromString(port.Identity()),
		})
	}

	sort.SliceStable(ports, func(i, j int) bool {
		left := ports[i]
		right := ports[j]

		return left.Port < right.Port
	})

	sort.SliceStable(resourceBelongsTo, func(i, j int) bool {
		left := resourceBelongsTo[i]
		right := resourceBelongsTo[j]

		return strings.Compare(left, right) < 0
	})

	annotations[ResourceBelongsToAnnotation] = strings.Join(resourceBelongsTo, ResourceBelongsToAnnotationValueDelimiter)

	return []*corev1.Service{{
		TypeMeta: serviceMeta,
		ObjectMeta: metav1.ObjectMeta{
			Name:        loadBalancerName,
			Annotations: annotations,
		},
		Spec: corev1.ServiceSpec{
			Ports:    ports,
			Selector: g.getSelector(),
			Type:     corev1.ServiceTypeLoadBalancer,
		},
	}}, nil
}

func (g *LoadBalancerServiceGenerator) GetSelector(service model.Service) PodSelector {
	if !g.isLoadBalanced(service) {
		return PodSelector{}
	}

	return g.getSelector()
}

func (g *LoadBalancerServiceGenerator) getSelector() PodSelector {
	return PodSelector{loadBalancerPodSelector: loadBalancerPodSelectorValue}
}

func (g *LoadBalancerServiceGenerator) isLoadBalanced(service model.Service) bool {
	return service.Labels[model.DropAppServiceLabel] == model.DropAppServiceLoadBalancer
}
